// Copyright 2014 PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "../../include/pdfwindow/PDFWindow.h"
#include "../../include/pdfwindow/PWL_Wnd.h"
#include "../../include/pdfwindow/PWL_EditCtrl.h"
#include "../../include/pdfwindow/PWL_ScrollBar.h"
#include "../../include/pdfwindow/PWL_Utils.h"
#include "../../include/pdfwindow/PWL_Caret.h"
#include "../../include/pdfwindow/PWL_FontMap.h"

#define IsFloatZero(f)                      ((f) < 0.0001 && (f) > -0.0001)
#define IsFloatBigger(fa,fb)                ((fa) > (fb) && !IsFloatZero((fa) - (fb)))
#define IsFloatSmaller(fa,fb)               ((fa) < (fb) && !IsFloatZero((fa) - (fb)))
#define IsFloatEqual(fa,fb)                 IsFloatZero((fa)-(fb))

/* ---------------------------- CPWL_EditCtrl ------------------------------ */

CPWL_EditCtrl::CPWL_EditCtrl() :
    m_pEdit(NULL),
    m_pEditCaret(NULL),
    m_bMouseDown(FALSE),
    m_pEditNotify(NULL),
    m_nCharSet(DEFAULT_CHARSET),
    m_nCodePage(0)
{
    m_pEdit = IFX_Edit::NewEdit();
    ASSERT(m_pEdit != NULL);
}

CPWL_EditCtrl::~CPWL_EditCtrl()
{
    IFX_Edit::DelEdit(m_pEdit);
}

void CPWL_EditCtrl::OnCreate(PWL_CREATEPARAM& cp)
{
    cp.eCursorType = FXCT_VBEAM;
}

void CPWL_EditCtrl::OnCreated()
{
    SetFontSize(GetCreationParam().fFontSize);

    m_pEdit->SetFontMap(GetFontMap());
    m_pEdit->SetNotify(this);
    m_pEdit->Initialize();
}

FX_BOOL CPWL_EditCtrl::IsWndHorV()
{
    CPDF_Matrix mt = GetWindowMatrix();
    CPDF_Point point1(0,1);
    CPDF_Point point2(1,1);

    mt.Transform(point1.x, point1.y);
    mt.Transform(point2.x, point2.y);

    return point2.y == point1.y;
}

void CPWL_EditCtrl::SetCursor()
{
    if (IsValid())
    {
        if (IFX_SystemHandler* pSH = GetSystemHandler())
        {
            if (IsWndHorV())
                pSH->SetCursor(FXCT_VBEAM);
            else
                pSH->SetCursor(FXCT_HBEAM);
        }
    }
}

void CPWL_EditCtrl::RePosChildWnd()
{
    m_pEdit->SetPlateRect(GetClientRect());
}

void CPWL_EditCtrl::OnNotify(CPWL_Wnd* pWnd, FX_DWORD msg, intptr_t wParam, intptr_t lParam)
{
    CPWL_Wnd::OnNotify(pWnd,msg,wParam,lParam);

    switch (msg)
    {
        case PNM_SETSCROLLINFO:
            switch (wParam)
            {
                case SBT_VSCROLL:
                    if (CPWL_Wnd * pChild = GetVScrollBar())
                    {
                        pChild->OnNotify(pWnd,PNM_SETSCROLLINFO,wParam,lParam);
                    }
                    break;
            }
            break;
        case PNM_SETSCROLLPOS:
            switch (wParam)
            {
                case SBT_VSCROLL:
                    if (CPWL_Wnd * pChild = GetVScrollBar())
                    {
                        pChild->OnNotify(pWnd,PNM_SETSCROLLPOS,wParam,lParam);
                    }
                    break;
            }
            break;
        case PNM_SCROLLWINDOW:
            {
                FX_FLOAT fPos = *(FX_FLOAT*)lParam;
                switch (wParam)
                {
                    case SBT_VSCROLL:
                        m_pEdit->SetScrollPos(CPDF_Point(m_pEdit->GetScrollPos().x,fPos));
                        break;
                }
            }
            break;
        case PNM_SETCARETINFO:
            {
                if (PWL_CARET_INFO * pCaretInfo = (PWL_CARET_INFO *)wParam)
                {
                    SetCaret(pCaretInfo->bVisible,
                        pCaretInfo->ptHead,
                        pCaretInfo->ptFoot);
                }
            }
            break;
    }
}

void CPWL_EditCtrl::CreateChildWnd(const PWL_CREATEPARAM & cp)
{
    if (!IsReadOnly())
        CreateEditCaret(cp);
}

void CPWL_EditCtrl::CreateEditCaret(const PWL_CREATEPARAM & cp)
{
    if (!m_pEditCaret)
    {
        m_pEditCaret = new CPWL_Caret;
        m_pEditCaret->SetInvalidRect(GetClientRect());

        PWL_CREATEPARAM ecp = cp;
        ecp.pParentWnd = this;
        ecp.dwFlags = PWS_CHILD | PWS_NOREFRESHCLIP;
        ecp.dwBorderWidth = 0;
        ecp.nBorderStyle = PBS_SOLID;
        ecp.rcRectWnd = CPDF_Rect(0,0,0,0);

        m_pEditCaret->Create(ecp);
    }
}

void CPWL_EditCtrl::SetFontSize(FX_FLOAT fFontSize)
{
    m_pEdit->SetFontSize(fFontSize);
}

FX_FLOAT CPWL_EditCtrl::GetFontSize() const
{
    return m_pEdit->GetFontSize();
}

FX_BOOL CPWL_EditCtrl::OnKeyDown(FX_WORD nChar, FX_DWORD nFlag)
{
    if (m_bMouseDown) return TRUE;

    FX_BOOL bRet = CPWL_Wnd::OnKeyDown(nChar,nFlag);

    //FILTER
    switch (nChar)
    {
    default:
        return FALSE;
    case FWL_VKEY_Delete:
    case FWL_VKEY_Up:
    case FWL_VKEY_Down:
    case FWL_VKEY_Left:
    case FWL_VKEY_Right:
    case FWL_VKEY_Home:
    case FWL_VKEY_End:
    case FWL_VKEY_Insert:
    case 'C':
    case 'V':
    case 'X':
    case 'A':
    case 'Z':
    case 'c':
    case 'v':
    case 'x':
    case 'a':
    case 'z':
        break;
    }

    if (nChar == FWL_VKEY_Delete)
    {
        if (m_pEdit->IsSelected())
            nChar = FWL_VKEY_Unknown;
    }

    switch (nChar)
    {
        case FWL_VKEY_Delete:
            Delete();
            return TRUE;
        case FWL_VKEY_Insert:
            if (IsSHIFTpressed(nFlag))
                PasteText();
            return TRUE;
        case FWL_VKEY_Up:
            m_pEdit->OnVK_UP(IsSHIFTpressed(nFlag),FALSE);
            return TRUE;
        case FWL_VKEY_Down:
            m_pEdit->OnVK_DOWN(IsSHIFTpressed(nFlag),FALSE);
            return TRUE;
        case FWL_VKEY_Left:
            m_pEdit->OnVK_LEFT(IsSHIFTpressed(nFlag),FALSE);
            return TRUE;
        case FWL_VKEY_Right:
            m_pEdit->OnVK_RIGHT(IsSHIFTpressed(nFlag),FALSE);
            return TRUE;
        case FWL_VKEY_Home:
            m_pEdit->OnVK_HOME(IsSHIFTpressed(nFlag),IsCTRLpressed(nFlag));
            return TRUE;
        case FWL_VKEY_End:
            m_pEdit->OnVK_END(IsSHIFTpressed(nFlag),IsCTRLpressed(nFlag));
            return TRUE;
        case FWL_VKEY_Unknown:
            if (!IsSHIFTpressed(nFlag))
                Clear();
            else
                CutText();
            return TRUE;
        default:
            break;
    }

    return bRet;
}

FX_BOOL CPWL_EditCtrl::OnChar(FX_WORD nChar, FX_DWORD nFlag)
{
    if (m_bMouseDown) return TRUE;

    CPWL_Wnd::OnChar(nChar,nFlag);

    //FILTER
    switch (nChar)
    {
        case 0x0A:
        case 0x1B:
            return FALSE;
        default:
            break;
    }

    FX_BOOL bCtrl = IsCTRLpressed(nFlag);
    FX_BOOL bAlt = IsALTpressed(nFlag);
    FX_BOOL bShift = IsSHIFTpressed(nFlag);

    FX_WORD word = nChar;

    if (bCtrl && !bAlt)
    {
        switch (nChar)
        {
            case 'C' - 'A' + 1:
                CopyText();
                return TRUE;
            case 'V' - 'A' + 1:
                PasteText();
                return TRUE;
            case 'X' - 'A' + 1:
                CutText();
                return TRUE;
            case 'A' - 'A' + 1:
                SelectAll();
                return TRUE;
            case 'Z' - 'A' + 1:
                if (bShift)
                    Redo();
                else
                    Undo();
                return TRUE;
            default:
                if (nChar < 32)
                    return FALSE;
        }
    }

    if (IsReadOnly()) return TRUE;

    if (m_pEdit->IsSelected() && word ==  FWL_VKEY_Back)
        word = FWL_VKEY_Unknown;

    Clear();

    switch (word)
    {
    case FWL_VKEY_Back:
        Backspace();
        break;
    case FWL_VKEY_Return:
        InsertReturn();
        break;
    case FWL_VKEY_Unknown:
        break;
    default:
        if (IsINSERTpressed(nFlag))
            Delete();
        InsertWord(word, GetCharSet());
        break;
    }

    return TRUE;
}

FX_BOOL CPWL_EditCtrl::OnLButtonDown(const CPDF_Point & point, FX_DWORD nFlag)
{
    CPWL_Wnd::OnLButtonDown(point,nFlag);

    if (ClientHitTest(point))
    {
        if (m_bMouseDown)
            InvalidateRect();

        m_bMouseDown = TRUE;
        SetCapture();

        m_pEdit->OnMouseDown(point,IsSHIFTpressed(nFlag),IsCTRLpressed(nFlag));
    }

    return TRUE;
}

FX_BOOL CPWL_EditCtrl::OnLButtonUp(const CPDF_Point & point, FX_DWORD nFlag)
{
    CPWL_Wnd::OnLButtonUp(point,nFlag);

    if (m_bMouseDown)
    {
        //can receive keybord message
        if (ClientHitTest(point) && !IsFocused())
            SetFocus();

        ReleaseCapture();
        m_bMouseDown = FALSE;
    }

    return TRUE;
}

FX_BOOL CPWL_EditCtrl::OnMouseMove(const CPDF_Point & point, FX_DWORD nFlag)
{
    CPWL_Wnd::OnMouseMove(point,nFlag);

    if (m_bMouseDown)
        m_pEdit->OnMouseMove(point,FALSE,FALSE);

    return TRUE;
}

CPDF_Rect CPWL_EditCtrl::GetContentRect() const
{
    return m_pEdit->GetContentRect();
}

void CPWL_EditCtrl::SetEditCaret(FX_BOOL bVisible)
{
    CPDF_Point ptHead(0,0),ptFoot(0,0);

    if (bVisible)
    {
        GetCaretInfo(ptHead,ptFoot);
    }

    CPVT_WordPlace wpTemp = m_pEdit->GetCaretWordPlace();
    IOnSetCaret(bVisible,ptHead,ptFoot,wpTemp);
}

void CPWL_EditCtrl::GetCaretInfo(CPDF_Point & ptHead, CPDF_Point & ptFoot) const
{
    if (IFX_Edit_Iterator * pIterator = m_pEdit->GetIterator())
    {
        pIterator->SetAt(m_pEdit->GetCaret());
        CPVT_Word word;
        CPVT_Line line;
        if (pIterator->GetWord(word))
        {
            ptHead.x = word.ptWord.x + word.fWidth;
            ptHead.y = word.ptWord.y + word.fAscent;
            ptFoot.x = word.ptWord.x + word.fWidth;
            ptFoot.y = word.ptWord.y + word.fDescent;
        }
        else if (pIterator->GetLine(line))
        {
            ptHead.x = line.ptLine.x;
            ptHead.y = line.ptLine.y + line.fLineAscent;
            ptFoot.x = line.ptLine.x;
            ptFoot.y = line.ptLine.y + line.fLineDescent;
        }
    }
}

void CPWL_EditCtrl::GetCaretPos(int32_t& x, int32_t& y) const
{
    CPDF_Point ptHead(0,0), ptFoot(0,0);

    GetCaretInfo(ptHead,ptFoot);

    PWLtoWnd(ptHead, x, y);
}

void CPWL_EditCtrl::SetCaret(FX_BOOL bVisible, const CPDF_Point & ptHead, const CPDF_Point & ptFoot)
{
    if (m_pEditCaret)
    {
        if (!IsFocused() || m_pEdit->IsSelected())
            bVisible = FALSE;

        m_pEditCaret->SetCaret(bVisible, ptHead, ptFoot);
    }
}

FX_BOOL CPWL_EditCtrl::IsModified() const
{
    return m_pEdit->IsModified();
}

CFX_WideString CPWL_EditCtrl::GetText() const
{
    return m_pEdit->GetText();
}

void CPWL_EditCtrl::SetSel(int32_t nStartChar,int32_t nEndChar)
{
    m_pEdit->SetSel(nStartChar, nEndChar);
}

void CPWL_EditCtrl::GetSel(int32_t & nStartChar, int32_t & nEndChar ) const
{
    m_pEdit->GetSel(nStartChar, nEndChar);
}

void CPWL_EditCtrl::Clear()
{
    if (!IsReadOnly())
        m_pEdit->Clear();
}

void CPWL_EditCtrl::SelectAll()
{
    m_pEdit->SelectAll();
}

void CPWL_EditCtrl::Paint()
{
    if (m_pEdit)
        m_pEdit->Paint();
}

void CPWL_EditCtrl::EnableRefresh(FX_BOOL bRefresh)
{
    if (m_pEdit)
        m_pEdit->EnableRefresh(bRefresh);
}

int32_t CPWL_EditCtrl::GetCaret() const
{
    if (m_pEdit)
        return m_pEdit->GetCaret();

    return -1;
}

void CPWL_EditCtrl::SetCaret(int32_t nPos)
{
    if (m_pEdit)
        m_pEdit->SetCaret(nPos);
}

int32_t CPWL_EditCtrl::GetTotalWords() const
{
    if (m_pEdit)
        return m_pEdit->GetTotalWords();

    return 0;
}

void CPWL_EditCtrl::SetScrollPos(const CPDF_Point& point)
{
    if (m_pEdit)
        m_pEdit->SetScrollPos(point);
}

CPDF_Point CPWL_EditCtrl::GetScrollPos() const
{
    if (m_pEdit)
        return m_pEdit->GetScrollPos();

    return CPDF_Point(0.0f, 0.0f);
}

CPDF_Font * CPWL_EditCtrl::GetCaretFont() const
{
    int32_t nFontIndex = 0;

    if (IFX_Edit_Iterator * pIterator = m_pEdit->GetIterator())
    {
        pIterator->SetAt(m_pEdit->GetCaret());
        CPVT_Word word;
        CPVT_Section section;
        if (pIterator->GetWord(word))
        {
            nFontIndex = word.nFontIndex;
        }
        else if (HasFlag(PES_RICH))
        {
            if (pIterator->GetSection(section))
            {
                nFontIndex = section.WordProps.nFontIndex;
            }
        }
    }

    if (IFX_Edit_FontMap* pFontMap = GetFontMap())
        return pFontMap->GetPDFFont(nFontIndex);

    return NULL;
}

FX_FLOAT CPWL_EditCtrl::GetCaretFontSize() const
{
    FX_FLOAT fFontSize = GetFontSize();

    if (IFX_Edit_Iterator * pIterator = m_pEdit->GetIterator())
    {
        pIterator->SetAt(m_pEdit->GetCaret());
        CPVT_Word word;
        CPVT_Section section;
        if (pIterator->GetWord(word))
        {
            fFontSize = word.fFontSize;
        }
        else if (HasFlag(PES_RICH))
        {
            if (pIterator->GetSection(section))
            {
                fFontSize = section.WordProps.fFontSize;
            }
        }
    }

    return fFontSize;
}

void CPWL_EditCtrl::SetText(const FX_WCHAR* csText)
{
    m_pEdit->SetText(csText);
}

void CPWL_EditCtrl::CopyText()
{
}

void CPWL_EditCtrl::PasteText()
{
}

void CPWL_EditCtrl::CutText()
{
}

void CPWL_EditCtrl::ShowVScrollBar(FX_BOOL bShow)
{
}

void CPWL_EditCtrl::InsertText(const FX_WCHAR* csText)
{
    if (!IsReadOnly())
        m_pEdit->InsertText(csText);
}

void CPWL_EditCtrl::InsertWord(FX_WORD word, int32_t nCharset)
{
    if (!IsReadOnly())
        m_pEdit->InsertWord(word, nCharset);
}

void CPWL_EditCtrl::InsertReturn()
{
    if (!IsReadOnly())
        m_pEdit->InsertReturn();
}

void CPWL_EditCtrl::Delete()
{
    if (!IsReadOnly())
        m_pEdit->Delete();
}

void CPWL_EditCtrl::Backspace()
{
    if (!IsReadOnly())
        m_pEdit->Backspace();
}

FX_BOOL CPWL_EditCtrl::CanUndo() const
{
    return !IsReadOnly() && m_pEdit->CanUndo();
}

FX_BOOL CPWL_EditCtrl::CanRedo() const
{
    return !IsReadOnly() && m_pEdit->CanRedo();
}

void CPWL_EditCtrl::Redo()
{
    if (CanRedo())
        m_pEdit->Redo();
}

void CPWL_EditCtrl::Undo()
{
    if (CanUndo())
        m_pEdit->Undo();
}

void CPWL_EditCtrl::IOnSetScrollInfoY(FX_FLOAT fPlateMin, FX_FLOAT fPlateMax,
                                                FX_FLOAT fContentMin, FX_FLOAT fContentMax,
                                                FX_FLOAT fSmallStep, FX_FLOAT fBigStep)
{
    PWL_SCROLL_INFO Info;

    Info.fPlateWidth = fPlateMax - fPlateMin;
    Info.fContentMin = fContentMin;
    Info.fContentMax = fContentMax;
    Info.fSmallStep = fSmallStep;
    Info.fBigStep = fBigStep;

    OnNotify(this,PNM_SETSCROLLINFO,SBT_VSCROLL,(intptr_t)&Info);

    if (IsFloatBigger(Info.fPlateWidth,Info.fContentMax-Info.fContentMin)
        || IsFloatEqual(Info.fPlateWidth,Info.fContentMax-Info.fContentMin))
    {
        ShowVScrollBar(FALSE);
    }
    else
    {
        ShowVScrollBar(TRUE);
    }
}

void CPWL_EditCtrl::IOnSetScrollPosY(FX_FLOAT fy)
{
    OnNotify(this, PNM_SETSCROLLPOS,SBT_VSCROLL, (intptr_t)&fy);
}

void CPWL_EditCtrl::IOnSetCaret(FX_BOOL bVisible, const CPDF_Point & ptHead, const CPDF_Point & ptFoot, const CPVT_WordPlace& place)
{
    PWL_CARET_INFO cInfo;
    cInfo.bVisible = bVisible;
    cInfo.ptHead = ptHead;
    cInfo.ptFoot = ptFoot;

    OnNotify(this, PNM_SETCARETINFO, (intptr_t)&cInfo, (intptr_t)NULL);
}

void CPWL_EditCtrl::IOnCaretChange(const CPVT_SecProps & secProps, const CPVT_WordProps & wordProps)
{
}

void CPWL_EditCtrl::IOnContentChange(const CPDF_Rect& rcContent)
{
    if (IsValid())
    {
        if (m_pEditNotify)
        {
            m_pEditNotify->OnContentChange(rcContent);
        }
    }
}

void CPWL_EditCtrl::IOnInvalidateRect(CPDF_Rect * pRect)
{
    InvalidateRect(pRect);
}

int32_t CPWL_EditCtrl::GetCharSet() const
{
    return m_nCharSet < 0 ? DEFAULT_CHARSET : m_nCharSet;
}

void CPWL_EditCtrl::GetTextRange(const CPDF_Rect& rect, int32_t & nStartChar, int32_t & nEndChar) const
{
    nStartChar = m_pEdit->WordPlaceToWordIndex(m_pEdit->SearchWordPlace(CPDF_Point(rect.left, rect.top)));
    nEndChar = m_pEdit->WordPlaceToWordIndex(m_pEdit->SearchWordPlace(CPDF_Point(rect.right, rect.bottom)));
}

CFX_WideString CPWL_EditCtrl::GetText(int32_t & nStartChar, int32_t & nEndChar) const
{
    CPVT_WordPlace wpStart = m_pEdit->WordIndexToWordPlace(nStartChar);
    CPVT_WordPlace wpEnd = m_pEdit->WordIndexToWordPlace(nEndChar);
    return m_pEdit->GetRangeText(CPVT_WordRange(wpStart, wpEnd));
}

void    CPWL_EditCtrl::SetReadyToInput()
{
    if (m_bMouseDown)
    {
        ReleaseCapture();
        m_bMouseDown = FALSE;
    }
}
